from examples.example_2_branching_multistep.ex_2_model_classes import Trial, g
from vidigi.animation import animate_activity_log
import pandas as pd
import plotly.io as pio
pio.renderers.default = "notebook"
import osExample 2: Branching + Multi-step Case
View Imported Code
import random
import numpy as np
import pandas as pd
import simpy
from sim_tools.distributions import Exponential, Lognormal, Uniform, Normal, Bernoulli
from vidigi.utils import populate_store
from examples.simulation_utility_functions import trace
# Class to store global parameter values. We don't create an instance of this
# class - we just refer to the class blueprint itself to access the numbers
# inside.
class g:
'''
Create a scenario to parameterise the simulation model
Parameters:
-----------
random_number_set: int, optional (default=DEFAULT_RNG_SET)
Set to control the initial seeds of each stream of pseudo
random numbers used in the model.
n_triage: int
The number of triage cubicles
n_reg: int
The number of registration clerks
n_exam: int
The number of examination rooms
n_trauma: int
The number of trauma bays for stablisation
n_cubicles_non_trauma_treat: int
The number of non-trauma treatment cubicles
n_cubicles_trauma_treat: int
The number of trauma treatment cubicles
triage_mean: float
Mean duration of the triage distribution (Exponential)
reg_mean: float
Mean duration of the registration distribution (Lognormal)
reg_var: float
Variance of the registration distribution (Lognormal)
exam_mean: float
Mean of the examination distribution (Normal)
exam_var: float
Variance of the examination distribution (Normal)
trauma_mean: float
Mean of the trauma stabilisation distribution (Exponential)
trauma_treat_mean: float
Mean of the trauma cubicle treatment distribution (Lognormal)
trauma_treat_var: float
Variance of the trauma cubicle treatment distribution (Lognormal)
non_trauma_treat_mean: float
Mean of the non trauma treatment distribution
non_trauma_treat_var: float
Variance of the non trauma treatment distribution
non_trauma_treat_p: float
Probability non trauma patient requires treatment
prob_trauma: float
probability that a new arrival is a trauma patient.
'''
random_number_set = 42
n_triage=2
n_reg=2
n_exam=3
n_trauma=4
n_cubicles_non_trauma_treat=4
n_cubicles_trauma_treat=5
triage_mean=6
reg_mean=8
reg_var=2
exam_mean=16
exam_var=3
trauma_mean=90
trauma_treat_mean=30
trauma_treat_var=4
non_trauma_treat_mean=13.3
non_trauma_treat_var=2
non_trauma_treat_p=0.6
prob_trauma=0.12
arrival_df="ed_arrivals.csv"
sim_duration = 600
number_of_runs = 100
# Class representing patients coming in to the clinic.
class Patient:
'''
Class defining details for a patient entity
'''
def __init__(self, p_id):
'''
Constructor method
Params:
-----
identifier: int
a numeric identifier for the patient.
'''
self.identifier = p_id
# Time of arrival in model/at centre
self.arrival = -np.inf
# Total time in pathway
self.total_time = -np.inf
# Shared waits
self.wait_triage = -np.inf
self.wait_reg = -np.inf
self.wait_treat = -np.inf
# Non-trauma pathway - examination wait
self.wait_exam = -np.inf
# Trauma pathway - stabilisation wait
self.wait_trauma = -np.inf
# Shared durations
self.triage_duration = -np.inf
self.reg_duration = -np.inf
self.treat_duration = -np.inf
# Non-trauma pathway - examination duration
self.exam_duration = -np.inf
# Trauma pathway - stabilisation duration
self.trauma_duration = -np.inf
# Class representing our model of the clinic.
class Model:
'''
Simulates the simplest minor treatment process for a patient
1. Arrive
2. Examined/treated by nurse when one available
3. Discharged
'''
# Constructor to set up the model for a run. We pass in a run number when
# we create a new model.
def __init__(self, run_number):
# Create a SimPy environment in which everything will live
self.env = simpy.Environment()
self.event_log = []
# Create a patient counter (which we'll use as a patient ID)
self.patient_counter = 0
self.trauma_patients = []
self.non_trauma_patients = []
# Create our resources
self.init_resources()
# Store the passed in run number
self.run_number = run_number
# Create a new Pandas DataFrame that will store some results against
# the patient ID (which we'll use as the index).
self.results_df = pd.DataFrame()
self.results_df["Patient ID"] = [1]
self.results_df["Queue Time Cubicle"] = [0.0]
self.results_df["Time with Nurse"] = [0.0]
self.results_df.set_index("Patient ID", inplace=True)
# Create an attribute to store the mean queuing times across this run of
# the model
self.mean_q_time_cubicle = 0
# create distributions
# Triage duration
self.triage_dist = Exponential(g.triage_mean,
random_seed=self.run_number*g.random_number_set)
# Registration duration (non-trauma only)
self.reg_dist = Lognormal(g.reg_mean,
np.sqrt(g.reg_var),
random_seed=self.run_number*g.random_number_set)
# Evaluation (non-trauma only)
self.exam_dist = Normal(g.exam_mean,
np.sqrt(g.exam_var),
random_seed=self.run_number*g.random_number_set)
# Trauma/stablisation duration (trauma only)
self.trauma_dist = Exponential(g.trauma_mean,
random_seed=self.run_number*g.random_number_set)
# Non-trauma treatment
self.nt_treat_dist = Lognormal(g.non_trauma_treat_mean,
np.sqrt(g.non_trauma_treat_var),
random_seed=self.run_number*g.random_number_set)
# treatment of trauma patients
self.treat_dist = Lognormal(g.trauma_treat_mean,
np.sqrt(g.non_trauma_treat_var),
random_seed=self.run_number*g.random_number_set)
# probability of non-trauma patient requiring treatment
self.nt_p_treat_dist = Bernoulli(g.non_trauma_treat_p,
random_seed=self.run_number*g.random_number_set)
# probability of non-trauma versus trauma patient
self.p_trauma_dist = Bernoulli(g.prob_trauma,
random_seed=self.run_number*g.random_number_set)
# init sampling for non-stationary poisson process
self.init_nspp()
def init_nspp(self):
# read arrival profile
self.arrivals = pd.read_csv(g.arrival_df) # pylint: disable=attribute-defined-outside-init
self.arrivals['mean_iat'] = 60 / self.arrivals['arrival_rate']
# maximum arrival rate (smallest time between arrivals)
self.lambda_max = self.arrivals['arrival_rate'].max() # pylint: disable=attribute-defined-outside-init
# thinning exponential
self.arrival_dist = Exponential(60.0 / self.lambda_max, # pylint: disable=attribute-defined-outside-init
random_seed=self.run_number*g.random_number_set)
# thinning uniform rng
self.thinning_rng = Uniform(low=0.0, high=1.0, # pylint: disable=attribute-defined-outside-init
random_seed=self.run_number*g.random_number_set)
def init_resources(self):
'''
Init the number of resources
and store in the arguments container object
Resource list:
1. Nurses/treatment bays (same thing in this model)
'''
# Shared Resources
self.triage_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_triage,
simpy_store=self.triage_cubicles,
sim_env=self.env)
self.registration_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_reg,
simpy_store=self.registration_cubicles,
sim_env=self.env)
# Non-trauma
self.exam_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_exam,
simpy_store=self.exam_cubicles,
sim_env=self.env)
self.non_trauma_treatment_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_cubicles_non_trauma_treat,
simpy_store=self.non_trauma_treatment_cubicles,
sim_env=self.env)
# Trauma
self.trauma_stabilisation_bays = simpy.Store(self.env)
populate_store(num_resources=g.n_trauma,
simpy_store=self.trauma_stabilisation_bays,
sim_env=self.env)
self.trauma_treatment_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_cubicles_trauma_treat,
simpy_store=self.trauma_treatment_cubicles,
sim_env=self.env)
# A generator function that represents the DES generator for patient
# arrivals
def generator_patient_arrivals(self):
# We use an infinite loop here to keep doing this indefinitely whilst
# the simulation runs
while True:
t = int(self.env.now // 60) % self.arrivals.shape[0]
lambda_t = self.arrivals['arrival_rate'].iloc[t]
# set to a large number so that at least 1 sample taken!
u = np.Inf
interarrival_time = 0.0
# reject samples if u >= lambda_t / lambda_max
while u >= (lambda_t / self.lambda_max):
interarrival_time += self.arrival_dist.sample()
u = self.thinning_rng.sample()
# Freeze this instance of this function in place until the
# inter-arrival time we sampled above has elapsed. Note - time in
# SimPy progresses in "Time Units", which can represent anything
# you like (just make sure you're consistent within the model)
yield self.env.timeout(interarrival_time)
# Increment the patient counter by 1 (this means our first patient
# will have an ID of 1)
self.patient_counter += 1
# Create a new patient - an instance of the Patient Class we
# defined above. Remember, we pass in the ID when creating a
# patient - so here we pass the patient counter to use as the ID.
p = Patient(self.patient_counter)
trace(f'patient {self.patient_counter} arrives at: {self.env.now:.3f}')
self.event_log.append(
{'patient': self.patient_counter,
'pathway': 'Shared',
'event': 'arrival',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# sample if the patient is trauma or non-trauma
trauma = self.p_trauma_dist.sample()
# Tell SimPy to start up the attend_clinic generator function with
# this patient (the generator function that will model the
# patient's journey through the system)
# and store patient in list for later easy access
if trauma:
# create and store a trauma patient to update KPIs.
self.trauma_patients.append(p)
self.env.process(self.attend_trauma_pathway(p))
else:
# create and store a non-trauma patient to update KPIs.
self.non_trauma_patients.append(p)
self.env.process(self.attend_non_trauma_pathway(p))
# A generator function that represents the pathway for a patient going
# through the clinic.
# The patient object is passed in to the generator function so we can
# extract information from / record information to it
def attend_non_trauma_pathway(self, patient):
'''
simulates the non-trauma/minor treatment process for a patient
1. request and wait for sign-in/triage
2. patient registration
3. examination
4a. percentage discharged
4b. remaining percentage treatment then discharge
'''
# record the time of arrival and entered the triage queue
patient.arrival = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'queue',
'event': 'triage_wait_begins',
'time': self.env.now}
)
###################################################
# request sign-in/triage
triage_resource = yield self.triage_cubicles.get()
# record the waiting time for triage
patient.wait_triage = self.env.now - patient.arrival
trace(f'patient {patient.identifier} triaged to minors '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use',
'event': 'triage_begins',
'time': self.env.now,
'resource_id': triage_resource.id_attribute
}
)
# sample triage duration.
patient.triage_duration = self.triage_dist.sample()
yield self.env.timeout(patient.triage_duration)
trace(f'triage {patient.identifier} complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_triage:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use_end',
'event': 'triage_complete',
'time': self.env.now,
'resource_id': triage_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.triage_cubicles.put(triage_resource)
#########################################################
# record the time that entered the registration queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'queue',
'event': 'MINORS_registration_wait_begins',
'time': self.env.now}
)
#########################################################
# request registration clerk
registration_resource = yield self.registration_cubicles.get()
# record the waiting time for registration
patient.wait_reg = self.env.now - start_wait
trace(f'registration of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use',
'event': 'MINORS_registration_begins',
'time': self.env.now,
'resource_id': registration_resource.id_attribute
}
)
# sample registration duration.
patient.reg_duration = self.reg_dist.sample()
yield self.env.timeout(patient.reg_duration)
trace(f'patient {patient.identifier} registered at'
f'{self.env.now:.3f}; '
f'waiting time was {patient.wait_reg:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_registration_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': registration_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.registration_cubicles.put(registration_resource)
########################################################
# record the time that entered the evaluation queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_wait_begins',
'event_type': 'queue',
'time': self.env.now}
)
#########################################################
# request examination resource
examination_resource = yield self.exam_cubicles.get()
# record the waiting time for examination to begin
patient.wait_exam = self.env.now - start_wait
trace(f'examination of patient {patient.identifier} begins '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_begins',
'event_type': 'resource_use',
'time': self.env.now,
'resource_id': examination_resource.id_attribute
}
)
# sample examination duration.
patient.exam_duration = self.exam_dist.sample()
yield self.env.timeout(patient.exam_duration)
trace(f'patient {patient.identifier} examination complete '
f'at {self.env.now:.3f};'
f'waiting time was {patient.wait_exam:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': examination_resource.id_attribute}
)
# Resource is no longer in use, so put it back in
self.exam_cubicles.put(examination_resource)
############################################################################
# sample if patient requires treatment?
patient.require_treat = self.nt_p_treat_dist.sample() #pylint: disable=attribute-defined-outside-init
if patient.require_treat:
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'requires_treatment',
'event_type': 'attribute_assigned',
'time': self.env.now}
)
# record the time that entered the treatment queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_wait_begins',
'event_type': 'queue',
'time': self.env.now}
)
###################################################
# request treatment cubicle
non_trauma_treatment_resource = yield self.non_trauma_treatment_cubicles.get()
# record the waiting time for treatment
patient.wait_treat = self.env.now - start_wait
trace(f'treatment of patient {patient.identifier} begins '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_begins',
'event_type': 'resource_use',
'time': self.env.now,
'resource_id': non_trauma_treatment_resource.id_attribute
}
)
# sample treatment duration.
patient.treat_duration = self.nt_treat_dist.sample()
yield self.env.timeout(patient.treat_duration)
trace(f'patient {patient.identifier} treatment complete '
f'at {self.env.now:.3f};'
f'waiting time was {patient.wait_treat:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_ends',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': non_trauma_treatment_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.non_trauma_treatment_cubicles.put(non_trauma_treatment_resource)
##########################################################################
# Return to what happens to all patients, regardless of whether they were sampled as needing treatment
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Shared',
'event': 'depart',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# total time in system
patient.total_time = self.env.now - patient.arrival
def attend_trauma_pathway(self, patient):
'''
simulates the major treatment process for a patient
1. request and wait for sign-in/triage
2. trauma
3. treatment
'''
# record the time of arrival and entered the triage queue
patient.arrival = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'triage_wait_begins',
'time': self.env.now}
)
###################################################
# request sign-in/triage
triage_resource = yield self.triage_cubicles.get()
# record the waiting time for triage
patient.wait_triage = self.env.now - patient.arrival
trace(f'patient {patient.identifier} triaged to trauma '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'triage_begins',
'time': self.env.now,
'resource_id': triage_resource.id_attribute
}
)
# sample triage duration.
patient.triage_duration = self.triage_dist.sample()
yield self.env.timeout(patient.triage_duration)
trace(f'triage {patient.identifier} complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_triage:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'triage_complete',
'time': self.env.now,
'resource_id': triage_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.triage_cubicles.put(triage_resource)
###################################################
# record the time that entered the trauma queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'TRAUMA_stabilisation_wait_begins',
'time': self.env.now}
)
###################################################
# request trauma room
trauma_resource = yield self.trauma_stabilisation_bays.get()
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'TRAUMA_stabilisation_begins',
'time': self.env.now,
'resource_id': trauma_resource.id_attribute
}
)
# record the waiting time for trauma
patient.wait_trauma = self.env.now - start_wait
# sample stablisation duration.
patient.trauma_duration = self.trauma_dist.sample()
yield self.env.timeout(patient.trauma_duration)
trace(f'stabilisation of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'TRAUMA_stabilisation_complete',
'time': self.env.now,
'resource_id': trauma_resource.id_attribute
}
)
# Resource is no longer in use, so put it back in the store
self.trauma_stabilisation_bays.put(trauma_resource)
#######################################################
# record the time that patient entered the treatment queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'TRAUMA_treatment_wait_begins',
'time': self.env.now}
)
########################################################
# request treatment cubicle
trauma_treatment_resource = yield self.trauma_treatment_cubicles.get()
# record the waiting time for trauma
patient.wait_treat = self.env.now - start_wait
trace(f'treatment of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'TRAUMA_treatment_begins',
'time': self.env.now,
'resource_id': trauma_treatment_resource.id_attribute
}
)
# sample treatment duration.
patient.treat_duration = self.trauma_dist.sample()
yield self.env.timeout(patient.treat_duration)
trace(f'patient {patient.identifier} treatment complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_treat:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'TRAUMA_treatment_complete',
'time': self.env.now,
'resource_id': trauma_treatment_resource.id_attribute}
)
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Shared',
'event': 'depart',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# Resource is no longer in use, so put it back in the store
self.trauma_treatment_cubicles.put(trauma_treatment_resource)
#########################################################
# total time in system
patient.total_time = self.env.now - patient.arrival
# This method calculates results over a single run. Here we just calculate
# a mean, but in real world models you'd probably want to calculate more.
def calculate_run_results(self):
# Take the mean of the queuing times across patients in this run of the
# model.
self.mean_q_time_cubicle = self.results_df["Queue Time Cubicle"].mean()
# The run method starts up the DES entity generators, runs the simulation,
# and in turns calls anything we need to generate results for the run
def run(self):
# Start up our DES entity generators that create new patients. We've
# only got one in this model, but we'd need to do this for each one if
# we had multiple generators.
self.env.process(self.generator_patient_arrivals())
# Run the model for the duration specified in g class
self.env.run(until=g.sim_duration)
# Now the simulation run has finished, call the method that calculates
# run results
self.calculate_run_results()
self.event_log = pd.DataFrame(self.event_log)
self.event_log["run"] = self.run_number
return {'results': self.results_df, 'event_log': self.event_log}
# Class representing a Trial for our simulation - a batch of simulation runs.
class Trial:
# The constructor sets up a pandas dataframe that will store the key
# results from each run against run number, with run number as the index.
def __init__(self):
self.df_trial_results = pd.DataFrame()
self.df_trial_results["Run Number"] = [0]
self.df_trial_results["Arrivals"] = [0]
self.df_trial_results["Mean Queue Time Cubicle"] = [0.0]
self.df_trial_results.set_index("Run Number", inplace=True)
self.all_event_logs = []
# Method to run a trial
def run_trial(self):
# Run the simulation for the number of runs specified in g class.
# For each run, we create a new instance of the Model class and call its
# run method, which sets everything else in motion. Once the run has
# completed, we grab out the stored run results (just mean queuing time
# here) and store it against the run number in the trial results
# dataframe.
for run in range(g.number_of_runs):
random.seed(run)
my_model = Model(run)
model_outputs = my_model.run()
patient_level_results = model_outputs["results"]
event_log = model_outputs["event_log"]
self.df_trial_results.loc[run] = [
len(patient_level_results),
my_model.mean_q_time_cubicle,
]
# print(event_log)
self.all_event_logs.append(event_log)
self.all_event_logs = pd.concat(self.all_event_logs)my_trial = Trial()
my_trial.run_trial()my_trial.all_event_logs.head(50)| patient | pathway | event | event_type | time | resource_id | run | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | Shared | arrival | arrival_departure | 3.285355 | NaN | 0 |
| 1 | 1 | Non-Trauma | triage_wait_begins | queue | 3.285355 | NaN | 0 |
| 2 | 1 | Non-Trauma | triage_begins | resource_use | 3.285355 | 1.0 | 0 |
| 3 | 2 | Shared | arrival | arrival_departure | 3.289691 | NaN | 0 |
| 4 | 2 | Non-Trauma | triage_wait_begins | queue | 3.289691 | NaN | 0 |
| 5 | 2 | Non-Trauma | triage_begins | resource_use | 3.289691 | 2.0 | 0 |
| 6 | 1 | Non-Trauma | triage_complete | resource_use_end | 7.364946 | 1.0 | 0 |
| 7 | 1 | Non-Trauma | MINORS_registration_wait_begins | queue | 7.364946 | NaN | 0 |
| 8 | 1 | Non-Trauma | MINORS_registration_begins | resource_use | 7.364946 | 1.0 | 0 |
| 9 | 2 | Non-Trauma | triage_complete | resource_use_end | 9.407274 | 2.0 | 0 |
| 10 | 2 | Non-Trauma | MINORS_registration_wait_begins | queue | 9.407274 | NaN | 0 |
| 11 | 2 | Non-Trauma | MINORS_registration_begins | resource_use | 9.407274 | 2.0 | 0 |
| 12 | 1 | Non-Trauma | MINORS_registration_complete | resource_use_end | 15.418481 | 1.0 | 0 |
| 13 | 1 | Non-Trauma | MINORS_examination_wait_begins | queue | 15.418481 | NaN | 0 |
| 14 | 1 | Non-Trauma | MINORS_examination_begins | resource_use | 15.418481 | 1.0 | 0 |
| 15 | 2 | Non-Trauma | MINORS_registration_complete | resource_use_end | 17.104670 | 2.0 | 0 |
| 16 | 2 | Non-Trauma | MINORS_examination_wait_begins | queue | 17.104670 | NaN | 0 |
| 17 | 2 | Non-Trauma | MINORS_examination_begins | resource_use | 17.104670 | 2.0 | 0 |
| 18 | 1 | Non-Trauma | MINORS_examination_complete | resource_use_end | 31.636252 | 1.0 | 0 |
| 19 | 1 | Shared | depart | arrival_departure | 31.636252 | NaN | 0 |
| 20 | 2 | Non-Trauma | MINORS_examination_complete | resource_use_end | 32.875857 | 2.0 | 0 |
| 21 | 2 | Non-Trauma | requires_treatment | attribute_assigned | 32.875857 | NaN | 0 |
| 22 | 2 | Non-Trauma | MINORS_treatment_wait_begins | queue | 32.875857 | NaN | 0 |
| 23 | 2 | Non-Trauma | MINORS_treatment_begins | resource_use | 32.875857 | 1.0 | 0 |
| 24 | 3 | Shared | arrival | arrival_departure | 33.426168 | NaN | 0 |
| 25 | 3 | Non-Trauma | triage_wait_begins | queue | 33.426168 | NaN | 0 |
| 26 | 3 | Non-Trauma | triage_begins | resource_use | 33.426168 | 1.0 | 0 |
| 27 | 3 | Non-Trauma | triage_complete | resource_use_end | 33.545008 | 1.0 | 0 |
| 28 | 3 | Non-Trauma | MINORS_registration_wait_begins | queue | 33.545008 | NaN | 0 |
| 29 | 3 | Non-Trauma | MINORS_registration_begins | resource_use | 33.545008 | 1.0 | 0 |
| 30 | 4 | Shared | arrival | arrival_departure | 37.900548 | NaN | 0 |
| 31 | 4 | Non-Trauma | triage_wait_begins | queue | 37.900548 | NaN | 0 |
| 32 | 4 | Non-Trauma | triage_begins | resource_use | 37.900548 | 2.0 | 0 |
| 33 | 4 | Non-Trauma | triage_complete | resource_use_end | 37.914164 | 2.0 | 0 |
| 34 | 4 | Non-Trauma | MINORS_registration_wait_begins | queue | 37.914164 | NaN | 0 |
| 35 | 4 | Non-Trauma | MINORS_registration_begins | resource_use | 37.914164 | 2.0 | 0 |
| 36 | 3 | Non-Trauma | MINORS_registration_complete | resource_use_end | 42.359504 | 1.0 | 0 |
| 37 | 3 | Non-Trauma | MINORS_examination_wait_begins | queue | 42.359504 | NaN | 0 |
| 38 | 3 | Non-Trauma | MINORS_examination_begins | resource_use | 42.359504 | 3.0 | 0 |
| 39 | 4 | Non-Trauma | MINORS_registration_complete | resource_use_end | 45.938325 | 2.0 | 0 |
| 40 | 4 | Non-Trauma | MINORS_examination_wait_begins | queue | 45.938325 | NaN | 0 |
| 41 | 4 | Non-Trauma | MINORS_examination_begins | resource_use | 45.938325 | 1.0 | 0 |
| 42 | 2 | Non-Trauma | MINORS_treatment_ends | resource_use_end | 46.278797 | 1.0 | 0 |
| 43 | 2 | Shared | depart | arrival_departure | 46.278797 | NaN | 0 |
| 44 | 5 | Shared | arrival | arrival_departure | 51.770459 | NaN | 0 |
| 45 | 5 | Non-Trauma | triage_wait_begins | queue | 51.770459 | NaN | 0 |
| 46 | 5 | Non-Trauma | triage_begins | resource_use | 51.770459 | 1.0 | 0 |
| 47 | 5 | Non-Trauma | triage_complete | resource_use_end | 55.072516 | 1.0 | 0 |
| 48 | 5 | Non-Trauma | MINORS_registration_wait_begins | queue | 55.072516 | NaN | 0 |
| 49 | 5 | Non-Trauma | MINORS_registration_begins | resource_use | 55.072516 | 1.0 | 0 |
event_position_df = pd.DataFrame([
# {'event': 'arrival', 'x': 10, 'y': 250, 'label': "Arrival" },
# Triage - minor and trauma
{'event': 'triage_wait_begins',
'x': 160, 'y': 375, 'label': "Waiting for<br>Triage" },
{'event': 'triage_begins',
'x': 160, 'y': 315, 'resource':'n_triage', 'label': "Being Triaged" },
# Minors (non-trauma) pathway
{'event': 'MINORS_registration_wait_begins',
'x': 300, 'y': 145, 'label': "Waiting for<br>Registration" },
{'event': 'MINORS_registration_begins',
'x': 300, 'y': 85, 'resource':'n_reg', 'label':'Being<br>Registered' },
{'event': 'MINORS_examination_wait_begins',
'x': 465, 'y': 145, 'label': "Waiting for<br>Examination" },
{'event': 'MINORS_examination_begins',
'x': 465, 'y': 85, 'resource':'n_exam', 'label': "Being<br>Examined" },
{'event': 'MINORS_treatment_wait_begins',
'x': 630, 'y': 145, 'label': "Waiting for<br>Treatment" },
{'event': 'MINORS_treatment_begins',
'x': 630, 'y': 85, 'resource':'n_cubicles_non_trauma_treat', 'label': "Being<br>Treated" },
# Trauma pathway
{'event': 'TRAUMA_stabilisation_wait_begins',
'x': 300, 'y': 560, 'label': "Waiting for<br>Stabilisation" },
{'event': 'TRAUMA_stabilisation_begins',
'x': 300, 'y': 490, 'resource':'n_trauma', 'label': "Being<br>Stabilised" },
{'event': 'TRAUMA_treatment_wait_begins',
'x': 630, 'y': 560, 'label': "Waiting for<br>Treatment" },
{'event': 'TRAUMA_treatment_begins',
'x': 630, 'y': 490, 'resource':'n_cubicles_trauma_treat', 'label': "Being<br>Treated" },
{'event': 'exit',
'x': 670, 'y': 330, 'label': "Exit"}
])animate_activity_log(
event_log=my_trial.all_event_logs[my_trial.all_event_logs['run']==1],
event_position_df= event_position_df,
scenario=g(),
debug_mode=True,
setup_mode=True,
every_x_time_units=5,
include_play_button=True,
gap_between_entities=10,
gap_between_rows=20,
plotly_height=900,
plotly_width=1600,
override_x_max=700,
override_y_max=675,
icon_and_text_size=20,
wrap_queues_at=10,
step_snapshot_max=50,
limit_duration=g.sim_duration,
time_display_units="dhm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_2_branching_multistep/Full%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
)Animation function called at 00:26:00
Iteration through minute-by-minute logs complete 00:26:00
Snapshot df concatenation complete at 00:26:00
Reshaped animation dataframe finished construction at 00:26:01
Placement dataframe finished construction at 00:26:01
Output animation generation complete at 00:26:02
Total Time Elapsed: 2.07 seconds